from PyQt4 import QtGui, QtCore

from class_items import *


class RGraphicsView(QtGui.QGraphicsView):
    """Customized graphics view class"""
    def __init__(self, window, initSize):
        super(RGraphicsView, self).__init__()
        
        self.window = window
        self.scene = QtGui.QGraphicsScene()
        self.setSceneRect(0, 0, initSize.width(), initSize.height())
        self.setScene(self.scene)
        
        self.setRenderHint(QtGui.QPainter.Antialiasing)
        self.pen = QtGui.QPen(QtGui.QBrush(QtCore.Qt.black), 1)
        self.setCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
        self.setupConstants(window)
        self.drawView(window)
        self.colIndicatorLine = None
        self.alignmentLines = []
        self.shadowedRegions = {}
        if window.seqData:
            self.drawAlignmentLine(window)
    
    def resizeEvent(self, event):        
        pass
    
    def mouseReleaseEvent(self, event):        
        if not self.window.seqData or not self.alignUnitItems:
            determineActionsToEnable(None)
        else:
            item = self.scene.itemAt(self.mapToScene(event.pos()))
            if item:
                if item in self.colNumItems:
                    item.mouseReleaseEvent(event)
                    determineActionsToEnable(item)
                else:
                    for row in self.alignUnitItems:
                        if item in row:
                            item.mouseReleaseEvent(event)
                            break
                    else:
                        determineActionsToEnable(item)
            elif self.window.itemMoved:
                determineActionsToEnable(self.alignUnitItems[0][0])
                self.window.newSeqData = copy.deepcopy(self.window.seqData)
                self.window.newSelected = []
                for eachItem in self.scene.selectedItems():
                    if eachItem not in self.alignmentLines:
                        self.window.newSelected.append(getItemCoordinates(eachItem))
               
                self.window.undoAction.setEnabled(True)
                self.window.redoAction.setEnabled(True)
                mouseMoveCmd = MouseMoveCommand(self, self.window)
                self.window.undoStack.push(mouseMoveCmd)                
    
                for alignmentLine in self.alignmentLines:
                    if alignmentLine.scenePos().x() > self.frameMarginLeft + self.frameWidth: 
                        alignmentLine.setPos(self.frameMarginLeft + self.frameWidth, \
                                                       self.alignmentLinePosY)
                    
                newRegion = None
                shadowedRegionsCopy = copy.deepcopy(list(self.shadowedRegions.keys()))
                for shadowedRegion in shadowedRegionsCopy:
                    if shadowedRegion[0] >= self.window.seqData['maxSeqLen']:
                        self.scene.removeItem(self.shadowedRegions[shadowedRegion][1])
                        self.shadowedRegions.pop(shadowedRegion, None)
                    elif shadowedRegion[1] >= self.window.seqData['maxSeqLen'] - 1:
                        newRegion = (shadowedRegion[0], self.window.seqData['maxSeqLen'] - 1)
                        newColor = self.shadowedRegions[shadowedRegion][0]
                        self.scene.removeItem(self.shadowedRegions[shadowedRegion][1])
                        self.shadowedRegions.pop(shadowedRegion, None)
            
                if newRegion:
                    self.drawRegionShadow(self.window, newRegion, newColor)
                
            else:
                determineActionsToEnable(self.alignUnitItems[0][0])
    
    def setupConstants(self, window):        
        self.marginTop = window.plotParameters['top distance']
        self.marginLeft = window.plotParameters['left distance']
        self.ballRadius = window.plotParameters['ball radius']
        self.ballSpacing = window.plotParameters['ball spacing']

        self.frameMarginTop = 2 * self.marginTop + 4 * self.ballRadius + self.ballSpacing
        self.frameMarginLeft = self.marginLeft + 30
        
        self.legendMarginTop = self.marginTop
        self.legendMarginLeft = self.frameMarginLeft
        
        self.alignmentLinePosX = self.frameMarginLeft
        self.alignmentLinePosY = self.frameMarginTop - 2 * self.ballRadius
    
    def drawView(self, window):
        
        if not window.seqData:            
            pass
        else:            
            self.numSeq = window.seqData['num']
            self.maxCol = window.seqData['maxSeqLen']
            self.sequences = window.seqData['seq']
            
            self.drawFrame(window)
            self.drawLegend(window)
            self.drawSeqNum(window)
            self.drawColNum(window)
            self.drawAlignment(window)

    def drawFrame(self, window):

        self.frameWidth = self.maxCol * 2 * self.ballRadius
        self.frameHeight = self.numSeq * (2 * self.ballRadius + self.ballSpacing)
        
        self.frameLineTop = QtGui.QGraphicsLineItem(0, 0, self.frameWidth, 0, 
                                                    None, self.scene)
        self.frameLineTop.setPos(self.frameMarginLeft, self.frameMarginTop)
        self.frameLineTop.setPen(self.pen)
        
        self.frameLineLeft = QtGui.QGraphicsLineItem(0, 0, 0, self.frameHeight, 
                                                     None, self.scene)
        self.frameLineLeft.setPos(self.frameMarginLeft, self.frameMarginTop)
        self.frameLineLeft.setPen(self.pen)
        
        sceneRect = self.sceneRect()
        if self.frameMarginTop + self.frameHeight > sceneRect.height():
            self.setSceneRect(0, 0, sceneRect.width(), 
                              self.frameMarginTop + self.frameHeight + 50)
        
        sceneRect = self.sceneRect()
        if self.frameMarginLeft + self.frameWidth > sceneRect.width():
            self.setSceneRect(0, 0, self.frameMarginLeft + self.frameWidth + 50, 
                              sceneRect.height())
        
        window.currentViewSaved = False
    
    def drawLegend(self, window):
        
        property = window.propertyCurrent
        propertyColor = window.propertyColors[property]
        
        x = self.legendMarginLeft
        y = self.legendMarginTop
        
        self.legendItems = []
        propertyTypes = list(propertyColor.keys())
        
        if property != 'identity':
            propertyTypes.remove('undefined')
            categoryIntervals = sorted(list(window.aaSettings[property].values()))
            categories = window.aaSettings[property].keys()
            
            assert set(propertyTypes) == set(categories)
        
            propertyTypeList = []
            for interval in categoryIntervals:                
                for category in categories:                    
                    if window.aaSettings[property][category] == interval:
                        propertyTypeList.append(category)
                        break
            propertyTypes = propertyTypeList
        
        else:
            # propertyTypes.sort()
            propertyTypes = ['A', 'F', 'G', 'I', 'L', 'M', 'P', 'V', 'W', 'C', 
                             'N', 'Q', 'S', 'T', 'Y', 'D', 'E', 'H', 'K', 'R']
        
        propertyTextItem = QtGui.QGraphicsTextItem(
                                    'property: ' + window.propertyCurrent)
        textFont = QtGui.QFont(propertyTextItem.font())
        textFont.setBold(True)
        propertyTextItem.setFont(textFont)
        
        rect = propertyTextItem.boundingRect()
        rect.moveCenter(QtCore.QPointF(x + 2*self.ballRadius + rect.width() / 2, 
                                       y + self.ballRadius))
        propertyTextItem.setPos(rect.topLeft())
 
        self.scene.addItem(propertyTextItem)
        self.legendItems.append(propertyTextItem)
        
        x += rect.width() + 4 * self.ballRadius
        
        for propertyType in propertyTypes:
            
            legendImageItem = QtGui.QGraphicsEllipseItem(0, 0, 
                    2 * self.ballRadius, 2 * self.ballRadius, None, self.scene)
            legendImageItem.setPos(x, y)
            legendImageItem.setPen(QtGui.QPen(QtCore.Qt.NoPen))
            legendImageItem.setBrush(propertyColor[propertyType])
            
            legendTextItem = QtGui.QGraphicsTextItem(propertyType)
            rect = legendTextItem.boundingRect()
            rect.moveCenter(QtCore.QPointF(x + 2*self.ballRadius + \
                                        rect.width() / 2, y + self.ballRadius))
            legendTextItem.setPos(rect.topLeft())
            self.scene.addItem(legendTextItem)
            
            self.legendItems.append((legendImageItem, legendTextItem))
            x += 4 * self.ballRadius + rect.width()
        
        window.currentViewSaved = False
    
    def drawSeqNum(self, window):
        
        x = self.frameMarginLeft
        y = self.frameMarginTop
        
        self.seqNumItems = []
        
        for i in range(1, self.numSeq+1):
            
            seqNumItem = RSeqNumTextItem(window, str(i))
            seqNumItem.setAcceptHoverEvents(True)
            
            font = seqNumItem.font()
            font.setPointSize(9)
            seqNumItem.setFont(font)
            
            rect = seqNumItem.boundingRect()
            rect.moveCenter(QtCore.QPointF(x - rect.width() / 2, 
                                           y + self.ballRadius))
            seqNumItem.setPos(rect.topLeft())
            self.scene.addItem(seqNumItem)
            
            self.seqNumItems.append(seqNumItem)
            y += 2 * self.ballRadius + self.ballSpacing
        
        window.currentViewSaved = False
    
    def drawColNum(self, window):
        
        x = self.frameMarginLeft
        y = self.frameMarginTop
        
        self.colNumItems = []
        
        for i in range(5, self.maxCol+1, 5):
            
            colNumItem = RColNumTextItem(window, str(i))
            colNumItem.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
            
            font = colNumItem.font()
            font.setPointSize(9)
            colNumItem.setFont(font)
            
            rect = colNumItem.boundingRect()
            rect.moveCenter(QtCore.QPointF(x + 9 * self.ballRadius, 
                                           y - rect.height() / 2))
            colNumItem.setPos(rect.topLeft())
            self.scene.addItem(colNumItem)
            
            self.colNumItems.append(colNumItem)
            x += 10 * self.ballRadius
        
        window.currentViewSaved = False
    
    def drawAlignment(self, window):
        
        x = self.frameMarginLeft
        y = self.frameMarginTop
        
        self.alignUnitItems = []
        propertyColor = window.propertyColors[window.propertyCurrent]
        
        for seq in self.sequences:            
            alignLineItems = []
            
            for aa in seq:
                if aa == '-':
                    unitItem = RUnitGapItem(window, self.ballRadius)
                    unitItem.setPen(QtGui.QPen(QtCore.Qt.black, self.ballRadius / 3))
                else:
                    unitItem = RUnitBallItem(window, self.ballRadius)
                    unitItem.setPen(QtGui.QPen(QtCore.Qt.NoPen))
                    
                    if window.propertyCurrent == 'identity':
                        unitItem.setBrush(propertyColor[aa])
                    else:
                        propertyType = window.aaPropertyTypes[aa][window.propertyCurrent]
                        unitItem.setBrush(propertyColor[propertyType])
                        
                        if propertyType == 'undefined':
                            unitItem.setPen(QtGui.QPen(QtCore.Qt.black, self.ballRadius / 8))
                
                unitItem.setPos(x, y)
                unitItem.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
                unitItem.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
                
                alignLineItems.append(unitItem)
                x += 2 * self.ballRadius
            
            self.alignUnitItems.append(alignLineItems)
            x = self.frameMarginLeft
            y += 2 * self.ballRadius + self.ballSpacing
        
        self.lastSelectedLineHead = None
        window.currentViewSaved = False
        window.currentAlignmentSaved = False
    
    def drawAlignmentLine(self, window):
        
        alignmentLine = RGraphicsLineItem(window, 0, 0, 
                                    0, self.frameHeight + 4 * self.ballRadius)
        
        alignmentLine.setPen(QtGui.QPen(QtCore.Qt.black, self.ballRadius / 4))
        alignmentLine.setPos(self.alignmentLinePosX, self.alignmentLinePosY)
        
        alignmentLine.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
        alignmentLine.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
        alignmentLine.setAcceptHoverEvents(True)
        
        self.alignmentLines.append(alignmentLine)
    
    def drawRegionShadow(self, window, region, color):
        
        width = (region[1] - region[0] + 1) * 2 * self.ballRadius
        height = self.frameHeight
        x0 = region[0] * 2 * self.ballRadius + self.frameMarginLeft
        y0 = self.frameMarginTop
        
        p1 = QtCore.QPointF(0, 0)
        p2 = QtCore.QPointF(0, height)
        p3 = QtCore.QPointF(width, height)
        p4 = QtCore.QPointF(width, 0)
        p5 = QtCore.QPointF(width - 1 * self.ballRadius, - 1 * self.ballRadius)
        p6 = QtCore.QPointF(1 * self.ballRadius, - 1 * self.ballRadius)
        polygon = QtGui.QPolygonF([p1, p2, p3, p4, p5, p6])
        
        regionShadow = RGraphicsPolygonItem(window, polygon)
        regionShadow.setPen(QtGui.QPen(QtCore.Qt.NoPen))
        regionShadow.setPos(x0, y0)
        regionShadow.setBrush(color)
        regionShadow.setOpacity(0.6)
        regionShadow.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, False)
        
        self.shadowedRegions[region] = (color, regionShadow)


class RWidget(QtGui.QWidget):
    """
    Widget class to place the major view and scene
    This widget object will be set as central widget of the main window
    """
    def __init__(self, window):
        super(RWidget, self).__init__()
        
        self.window = window
        self.sizeHint()
        
        vbox = QtGui.QVBoxLayout()
        self.view = RGraphicsView(window, self.size())
        vbox.addWidget(self.view)
        self.setLayout(vbox)
    
    def sizeHint(self):
        
        rect = QtCore.QRect(0, 0, self.window.width(), self.window.height())
        self.setGeometry(rect)
        
        return QtCore.QSize(rect.width(), rect.height())
